/*******************************************************************************************
 *      @name   udp_client.c
 *      @brief  这是在linux系统下的使用UDP套接字实现UDP通信(客户端)的代码案例，主要学习UDP协议
 *      @date   2025/02/19
 *      @author cecelmx@126.com
 *      @note
 *              1.想要使用UDP套接字通信的前提是调用socket函数创建UDP套接字
 *                udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
 *
 *              2.UDP协议提供的是面向无连接、不可靠、固定长度的服务，无法确保数据包的交付
 *
 *              3.接收方必须调用bind函数把UDP套接字和主机信息(端口/IP地址)进行绑定
 *
 *              4.发送方需要调用sendto()函数发送数据，接收方需要调用recvfrom()函数接收数据
 *
 *      @version  xx.xx.xx   主版本号.次版本号.修正版本号
 *
 *      CopyRight (c)   2024-2025   Your Name     All Right Reserved
 *
 * ****************************************************************************************/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define BUF_SIZE 1024 // 接收缓冲区的大小

char recvbuf[BUF_SIZE]; // 接收缓冲区
int udp_socket;         // UDP套接字的文件描述符

#define MYPORT 6666 // 本主机的端口

// 接收线程的任务
void *recv_handler(void *arg)
{
    struct sockaddr_in src = {0}; // 用于记录发送方主机的信息(端口和IP地址)
    socklen_t srclen = sizeof(src);

    for (;;)
    {
        // 接收数据，未收到数据时会阻塞
        recvfrom(udp_socket, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&src, &srclen);

        // 输出数据
        printf("recv data from [%s] is [%s]\n", inet_ntoa(src.sin_addr), recvbuf);
    }
}

int main(int argc, char **argv)
{

    char sendbuf[BUF_SIZE] = {0}; // 用于存储待发送的数据

    // 用于记录服务器端的信息
    struct sockaddr_in dest = {0};
    dest.sin_family = AF_INET;                          // IPV4
    dest.sin_port = htons(atoi("8888"));                // 目标端口，需要转换为网络字节序
    dest.sin_addr.s_addr = inet_addr("192.168.60.104"); // 目标地址，需要转换为网络字节序

    // 1.创建UDP套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == udp_socket)
    {
        fprintf(stderr, "create udp socket error,%s\n", strerror(errno));
        exit(-1);
    }

    // 2.bind()是可选项，如果客户端需要接收来自其他主机的消息，则应该调用
    struct sockaddr_in myaddr = {0};
    myaddr.sin_family = AF_INET;         // IPV4
    myaddr.sin_port = htons(MYPORT);     // 本地端口，需要转换为网络字节序
    myaddr.sin_addr.s_addr = INADDR_ANY; // 本地地址，需要转换为网络字节序
    bind(udp_socket, (struct sockaddr *)&myaddr, sizeof(myaddr));

    // 3.创建一条接收线程，专门用于接收其他主机的消息
    pthread_t thread;
    pthread_create(&thread, NULL, recv_handler, NULL);

    // 4.死循环
    for (;;)
    {
        // 获取数据
        printf("请输入数据:");
        scanf("%s", sendbuf);

        // 发送数据
        sendto(udp_socket, sendbuf, BUF_SIZE, 0, (struct sockaddr *)&dest, sizeof(dest));
        printf("send OK\n");
    }

    // 5.关闭套接字
    close(udp_socket);

    return 0;
}
